import { observer } from "./observe/index";
import { nextTick } from "./utils/nextTick";

import Watcher from "./observe/watcher";
import { Dep } from "./observe/dep";

function myProxy(vm, source, key) {
  //vm实例上直接添加 vm._data中的属性,get 函数set进行劫持，数据来源vm._data
  Object.defineProperty(vm, key, {
    get() {
      return vm[source][key];
    },
    set(newValue) {
      vm[source][key] = newValue;
    },
  });
}

//入口1 初始化状态---暴露出方法--模块化开发
export function initState(vm) {
  const opts = vm.$options; // 获取用户配置选项
  if (opts.props) {
    //1.
    //判断有没有props属性 初始化props选项
  }
  if (opts.data) {
    //2. 初始化data选项
    //判断有没有data属性
    initData(vm);
  }
  if (opts.watch) {
    // 3.然后在走watch,myProxy代理在initData中已经实现，现在，在watch里面可以获取到对应的值
    initWatch(vm);
  }
  if (opts.computed) {
    initComputed(vm);
  }
  if (opts.methods) {
    initMethods(vm);
  }
}

function initProps() {}

function initData(vm) {
  // 1.data可能是对象 或者是函数
  let data = vm.$options.data;

  data = typeof data == "function" ? data.call(vm) : data; // call()、apply()、bind() 都是用来重定义 this 这个对象的！ call(this,1,2) apply(this,[1,2]) bind(this,[1,2])()
  vm._data = data;
  // //console.log(data,"dagta"); // 1拿到数据

  // 2.对数据data进行劫持
  observer(data); //2.1对对象进行劫持  对2.2数组进行劫持

  //3. 通过代理 可以直接通过vm（每个vue组件中的实例this）实例（vm.msg访问属性）而不用通过vm._data.msg进行访问 {a:1,b:2}
  for (let key in data) {
    myProxy(vm, "_data", key);
  }
}
// 代理函数
// 例子
// vm = {
//     _data: {
//         name: 'zz',
//         get name() {},
//         set name(newValue) {}
//     },
//     //代理过后会出现如下
//     name: 'zz',
//     get name() {},
//     set name(newValue) {}

// }

function initWatch(vm) {
  // 1. 获取到watch对象
  let watch = vm.$options.watch;
  console.log(vm, "张哈哈哈vm--initWatch--watch初始化方法");
  // 2. 实现watch对象里面所写的 监听对象的值的功能
  if (watch) {
    // 2.1 获取 他的属性对应的值 （数组，对象，字符，函数）基本四种情况分别进行处理
    for (let key in watch) {
      let handler = watch[key];
      // 2.2 判断val是否是 数组
      if (Array.isArray(handler)) {
        // 数组
        handler.forEach((item) => {
          createWatcher(vm, key, item);
        });
      } else {
        // 对象 字符 函数
        //3.0 创建一个方法来处理 --为了代码的整洁
        createWatcher(vm, key, handler);
      }
    }
  }
}
//声明函数
// vm.$watch(() => {return 'a'}) //这段代码的作用是 就是在watch上的属性
// vm 实例  expOrfn 属性名/表达式  handler 值  options是否是第一次初次渲染
// 格式化处理
function createWatcher(vm, exprOrfn, handler, options) {
  // 3.1 判断watch中属性 的值 是否是对象 aa: { handler(newval,oldval){}, immdediate: true,deep: true}
  if (Object.prototype.toString.call(handler) == "[object Object]") {
    options = handler; // 1.用户的配置项目,是否配置了deep 或者immediate
    handler = handler.handler; // 2.这是一个函数 // 注意1和2的顺序
  }

  // 3.3 aa:'myMethod' 判断val是否是字符串
  if (typeof handler == "string") {
    // 3.3.1 如果是字符串，则直接执行val
    handler = vm[handler]; // 将实例上的方法 作为val
    //3.3.2 方法的代理和 vm._data中的_data代理一样
  }

  // 3.4 aa:function(newval,oldval)其他的是函数
  // watch 最终处理$watch这个方法
  return vm.$watch(exprOrfn, handler, options);
}

function initComputed(vm) {
  console.log(vm, "initComputed--computed");
  //1.0 实现代理到vm上，实现vm上可以直接访问到计算属性
  // 2.0 计算属性 一般有两种写法 函数 或者对象形式 所以要分别进行处理，通过Object.defineProperty进行数据劫持到vm上
  let computed = vm.$options.computed || {};
  // 为每个计算属性 都添加一个watcher标识，当前计算属性都在哪里用到了，当数据更新的，通知，所有依赖计算属性的地方进行更新
  vm._computeWatcher = {};
  for (let key in computed) {
    let userRef = computed[key];
    console.log(userRef, "userDefuserDef--nitComputed--computed");
    let getterFunc = typeof userRef == "function" ? userRef : userRef.get;
    vm._computeWatcher[key] = new Watcher(
      vm,
      getterFunc,
      { lazy: true },
      () => {},
    ); //lazy表示当前new生成的watcher实例是 属于计算属性的 watcher---默认计算属性不渲染视图
    console.log(
      vm._computeWatcher[key],
      "vm._computeWatcher[key] vm._computeWatcher[key] ---computed",
    );
    defineComputed(vm, key, userRef);
  }
}

let objectWatch = {};
function defineComputed(target, key, value) {
  // value值
  // 给对象设置一下 拥有的默认属性功能
  objectWatch = {
    enumerable: true, // 可枚举的
    configurable: true, // 可否被修改
    get: () => {},
    set: () => {},
  };
  if (typeof value == "object") {
    objectWatch.set = value.set;
    // objectWatch.get = value.get
    objectWatch.get = createComputedGetter(key);
  } else if (typeof value == "function") {
    // objectWatch.get = value
    objectWatch.get = createComputedGetter(key);
  }
  // 利用Object.defineProperty特性 vm.属性  会会触发get函数
  Object.defineProperty(target, key, objectWatch);
}

// 抽出函数 高阶函数来 包裹 原有  get方法 来集中处理
function createComputedGetter(key) {
  return function () {
    // 这里的this是vm因为vm调用这个方法了
    //1.0 拿到当前计算属性的wacther
    let currentKeyWatcher = this._computeWatcher[key];
    console.log(
      currentKeyWatcher,
      "currentKeyWatchercurrentKeyWatcher-computed",
    );
    //2.0 实现缓存机制--可以获取到计算属性的值函数调用 返回的值
    if (currentKeyWatcher) {
      if (currentKeyWatcher.dirty) {
        currentKeyWatcher.evalte(); //调用获取计算属性的值--方法
      }
      console.log(Dep, Dep.target, "---computed--pre-0");
      // 3.0 计算属性watcher跑完之后（计算属性watcher和渲染watcher需要互相记忆===>相互存放watcher），然后才可以，继续判断有没有渲染watcher，有的话，表示当前计算属性watcher有用到界面，
      if (Dep.target) {
        currentKeyWatcher.depend(); // currentKeyWatcher计算属性watcher--收集我们的渲染watcher
      }
      return currentKeyWatcher.value; //否则 直接取已经运行过当前计算属性值方法--返回的值--来达到实现计算属性值方法，，缓存机制
    }
  };
}

function initMethods() {}
// 生命vm实例上的公共方法
export function stateMixin(Vue) {
  //2. 定义vm实例 Vue下的原型prototype的全局方法 $nextTick()
  // 2.1 列队 进行处理 $nextTick原理就是 1.把vue的自己的nextTick函数内传递的函数flushWatcher和2.用户使用时候的函数，进行合并
  // 该方法的作用是 给每个Vue实例 vm上添加一个方法 $nextTick
  Vue.prototype.$nextTick = function (cb) {
    // nextTick 作用 数据更新之后 获取最新的dom
    //   console.log(cb, '用户自己写的函数')
    nextTick(cb);
  };

  /***
   * 实现 $watch 方法
   * 参数Vue
   */
  //1. 实现$watch   watch的底层实现 全都是通过$watch
  Vue.prototype.$watch = function (exprOrfn, handler, options = {}) {
    // watch的实现 方法 就是通过 之前生命的watcher类 通过 new watcher来调用并且传递参数
    //
    // console.log(this,'watch----调用Vue.prototype原型上的公共方法时候，这个方法内可以拿到当前调用者实例vm')
    console.log(
      exprOrfn,
      "exprOrfn",
      handler,
      "handler----回调函数",
      options,
      "options",
      "watch---监听的原理 exprOrfn, handler, options",
    );
    // watch的核心 就算watcher类
    let watcher = new Watcher(
      this,
      exprOrfn,
      { ...options, user: true },
      handler,
    );
    console.log(watcher, "watch--watcher实例-watch监听器的实例");
    if (options && options.immediate) {
      // 如果存在immediate而且为true，那么立即执行
      handler.call(this);
    }
  };
}

//观察者模式： 一种设计模式，一个对象（主题）维护一个观察者列表，并在状态改变时---自动--通知他们
// 2. 关键组件： 主题： 维护观察者列表，提供用于增加或删除观察者的接口。  观察者： 提供一个更新接口，以便在主题状态改变时被通知
//3. 交互方式：观察者直接接收来自主题的更新。
// class Subject { //主题
//     constructor() {
//         this.observers = [];
//     }

//     subscribe(observer) {
//         this.observers.push(observer);
//     }

//     unsubscribe(observer) {
//         this.observers = this.observers.filter(obs => obs !== observer);
//     }

//     notify(data) {
//         this.observers.forEach(observer => observer.update(data));
//     }
// }

// class Observer { //观察者
//     update(data) {
//         console.log("Received data:", data);
//     }
// }
//使用
// let sub1 = new Subject()
// let obs = new Observer()
// sub1.subscribe(obs) // 添加一个观察者到 主题列表种
// sub1.notify('nihao') // 通知这个观察者--进行数据更新--全程知道主题是什么
// '"Received data:", nihao'
